package lode

import (
	"encoding/json"
	"github.com/Unixeno/islands/conf"
	"github.com/Unixeno/islands/logging"
	"io/ioutil"
	"os"
	"path/filepath"
	"sync"
	"sync/atomic"
	"time"
)

var logger = logging.GetLogger("lode")

func init() {
	level := logging.GetLevel(conf.GetString("LogLevel"))
	logger.SetLevel(level)
	initDB()
}

type db struct {
	images *imageDB
	layers *layerDB
}

type layerInfoEntry struct {
	Id         string    // layer-id，层文件的未压缩tar的sha256散列值
	Path       string    // 层的路径
	Size       int64     // 层文件的未压缩tar尺寸，单位字节
	ImportTime time.Time // 导入时间
	Counter    int32     // 被不同镜像使用次数
}

type imageInfoEntry struct {
	Id      string    // 镜像id，其值由镜像的配置文件的摘要生成
	Name    string    // 镜像名称
	Info    BaseImage // 镜像的详细信息
	Counter int32     // 被不同容器使用次数
}

type layerDB struct {
	syncDB sync.Map
}
type imageDB struct {
	syncDB sync.Map
}

var lodeDB db

func initDB() {
	lodeDB.images = new(imageDB)
	lodeDB.layers = new(layerDB)
	lodeDB.layers.init()
	lodeDB.images.init()
}

func (db *layerDB) init() {
	logger.Debug("load layers database from disk")
	mapDB := make(map[string]*layerInfoEntry)
	data, err := ioutil.ReadFile(filepath.Join(conf.GetString("RootPath"), "layerdb", "layers.json"))
	if err != nil {
		if os.IsExist(err) {
			logger.WithError(err).Error("failed to load layers database")
		} else {
			logger.WithError(err).Warning("layers database doesn't exist")
		}
	} else {
		if err = json.Unmarshal(data, &mapDB); err != nil {
			logger.WithError(err).Error("failed to parse layers database")
		}
		db.syncDB = layerMapToSyncMap(mapDB)
		logger.Printf("found %d layers", len(mapDB))
	}
}

func (db layerDB) store(DirPath string) {

}

// 将tar格式的层文件放入层仓库，同时返回层id
// 当层已经存在时直接返回层id
func (db *layerDB) storeTar(path, fileType string) (string, error) {
	id, err := getSha256(path)
	if err != nil {
		logger.WithError(err).Error("failed to calculate layer hash")
		return "", err
	}
	defer func() {
		if err == nil {
			db.save()
		}
	}()

	layerPath := filepath.Join(conf.GetString("RootPath"), "layerdb", id)
	if layer, ok := db.syncDB.LoadOrStore(id, &layerInfoEntry{
		Id:         id,
		Path:       layerPath,
		Size:       getSize(path),
		ImportTime: time.Now(),
		Counter:    1,
	}); ok {
		logger.WithField("id", id).Debug("layers already exist, ignore...")
		atomic.AddInt32(&layer.(*layerInfoEntry).Counter, 1)
		return id, nil
	} else {
		err = os.MkdirAll(filepath.Join(conf.GetString("RootPath"), "layerdb", id), 0700)
		if err != nil {
			logger.WithError(err).Error("an error occurred while creating layer directory")
			db.syncDB.Delete(id)
			return "", err
		}
		err = unTar(path, layerPath, fileType)
		if err != nil {
			db.syncDB.Delete(id)
			return "", err
		}

		return id, nil
	}
}

func (db *layerDB) save() {
	logger.Debug("saving layers database")
	mapDB := syncMapToLayerMap(db.syncDB)
	data, _ := json.Marshal(mapDB)
	err := ioutil.WriteFile(filepath.Join(conf.GetString("RootPath"), "layerdb", "layers.json"), data, 0600)
	if err != nil {
		logger.WithError(err).Error("cannot save layers database file to disk")
	}
}

func (db layerDB) getPath(id string) string {
	entry, ok := db.syncDB.Load(id)
	if ok {
		return entry.(*layerInfoEntry).Path
	} else {
		return ""
	}
}

func (db *imageDB) init() {
	logger.Debug("load images database from disk")
	mapDB := make(map[string]*imageInfoEntry)
	data, err := ioutil.ReadFile(filepath.Join(conf.GetString("RootPath"), "imagedb", "images.json"))
	if err != nil {
		if os.IsExist(err) {
			logger.WithError(err).Error("failed to load images database")
		} else {
			logger.WithError(err).Warning("images database doesn't exist")
		}
	} else {
		if err = json.Unmarshal(data, &mapDB); err != nil {
			logger.WithError(err).Error("failed to parse images database")
		}
		db.syncDB = imageMapToSyncMap(mapDB)
		logger.Printf("found %d images", len(mapDB))
	}
}

func (db *imageDB) store(info BaseImage, name string) (string, error) {
	raw, _ := json.Marshal(info)
	hash := getBytesSha256(raw)

	if name == "" {
		name = hash
	}

	logger.Debug("the image hash is " + hash)
	imageDBPath := filepath.Join(conf.GetString("RootPath"), "imagedb")
	// 确保写文件的时候目录存在
	_ = os.MkdirAll(imageDBPath, 0700)
	err := ioutil.WriteFile(filepath.Join(conf.GetString("RootPath"), "imagedb", hash+".json"), raw, 0600)
	if err != nil {
		return "", err
	}

	db.syncDB.Store(hash, &imageInfoEntry{
		Id:      hash,
		Name:    name,
		Info:    info,
		Counter: 0,
	})

	db.save()
	return hash, nil
}

func (db imageDB) save() {
	logger.Debug("saving images database")
	mapDB := syncMapToImageMap(db.syncDB)
	data, _ := json.Marshal(mapDB)
	err := ioutil.WriteFile(filepath.Join(conf.GetString("RootPath"), "imagedb", "images.json"), data, 0600)
	if err != nil {
		logger.WithError(err).Error("cannot save images database file to disk")
	}
}

//被容器使用之前要先进行注册
func (db imageDB) register(id string) error {
	if image, ok := db.syncDB.Load(id); ok {
		//image.(*imageInfoEntry).Counter ++
		//db.syncDB.Store(id, image)
		atomic.AddInt32(&image.(*imageInfoEntry).Counter, 1)
		db.save()
		logger.Debug(image.(*imageInfoEntry).Id, " registered success")
		return nil
	}
	return CustomizeError{Message: "The image not exist"}
}

//当容器被销毁要取消对其注册
func (db imageDB) unRegister(id string) error {
	if image, ok := db.syncDB.Load(id); ok {
		if image.(*imageInfoEntry).Counter > 0 {
			atomic.AddInt32(&image.(*imageInfoEntry).Counter, -1)
			db.save()
		}
		logger.Debug(image.(*imageInfoEntry).Id, " unRegister success")
		return nil
	}
	return CustomizeError{Message: "The image not exist"}
}

//根据id删除一个镜像
func RemoveImage(id string) error {
	return lodeDB.images.removeImage(id)
}

func (db imageDB) removeImage(id string) error {
	if image, ok := db.syncDB.Load(id); ok {
		if image.(*imageInfoEntry).Counter > 0 {
			//有容器在使用镜像
			return CustomizeError{Message: "The image was in use"}
		} else {
			//可以删除
			defer db.save()
			defer lodeDB.layers.save()
			layers := image.(*imageInfoEntry).Info.Rootfs
			for _, layer := range layers {
				tmp, _ := lodeDB.layers.syncDB.Load(layer)
				temp := tmp.(*layerInfoEntry)
				//temp.Counter--
				atomic.AddInt32(&temp.Counter, -1)
				if temp.Counter == 0 {
					//没有镜像使用此层
					path := lodeDB.layers.getPath(layer)
					if err := os.RemoveAll(path); err != nil {
						logger.Error("Can not remove layer : ", layer)
						return err
					}
					//delete(lodeDB.layers, layer)
					lodeDB.layers.syncDB.Delete(layer)
					logger.Info("layer ", layer, " has been removed")
				}
			}
			db.syncDB.Delete(id)
			if err := os.Remove(filepath.Join(conf.GetString("RootPath"), "imagedb", id+".json")); err != nil {
				return err
			}
			logger.Info("Image has been removed")
			return nil
		}
	} else {
		//不存在这个镜像
		return CustomizeError{Message: "The image doesn't exist"}
	}
}

func imageIsExist(name string) bool {
	_, ok := lodeDB.images.syncDB.Load(name)
	return ok
}
